package cn.edu.hit.kg;

import cn.edu.hit.kg.entity.*;
import jdk.nashorn.api.scripting.JSObject;
import jdk.nashorn.api.scripting.NashornException;
import net.sf.json.JSONObject;
import org.apache.commons.lang3.StringUtils;
import cn.edu.hit.core.Document;

import java.util.HashSet;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class FunctionUtil {
    private static List<MatchFunction> matchFunctions = null;

    @Deprecated
    public static boolean doMatch(JSONObject signal, Feature feature) {
        Object value = signal.get("value");
        List params = feature.get("params", BaseNode.listTransfer);

        BiFunction<Object, List, Boolean> biFunction = getFunction(feature, null, null, null);

        return biFunction.apply(value, params);
    }

    public static boolean doMatch(Signal signal, Feature feature, List<Signal> signals, String unitId) throws NumberFormatException {
        List params = feature.get("params", BaseNode.listTransfer);

        BiFunction<Object, List, Boolean> biFunction = getFunction(feature, null, signals,unitId);
        Boolean result;
        if (signal.getComponent() == null || !signal.getComponent().containsKey("id")) {
            result = biFunction.apply(null, params);
        } else {
            Object value = signal.getValue();
            result = biFunction.apply(value, params);
        }
//        System.out.println("signal = " + signal);
//        System.out.println("feature = " + feature);
//        System.out.println("result = " + result);

        if (result && signal.getSource() != null) {
            if (!feature.getChannelIds().has(unitId)) {
                feature.getChannelIds().push(unitId, new HashSet<>());
            }
            List<String> channels = SignalUtil.unzipSignalIdFromDocument(((Document) signal.getSource()));
            HashSet<String> channelSet = feature.getChannelIds(unitId);
            channelSet.addAll(channels);
            feature.getChannelIds().push(unitId, channelSet);
        }

        return result;
    }


    private static Pattern numberPattern = Pattern.compile("((-?\\d+)(\\.\\d+)?).*");

    private static double toNumber(Object o) throws NumberFormatException {
//        System.out.println(o.getClass().getName());

        if (o instanceof Number) {
            return ((Number) o).doubleValue();
        }
        if (o instanceof String) {
            Matcher matcher = numberPattern.matcher(StringUtils.trimToEmpty((String) o));
            double val = 0;
            if (matcher.find()) {
                val = Double.parseDouble(matcher.group(1));
            } else {
                System.out.println(o.toString() + "无法被识别为数值");
                throw new NumberFormatException(o.toString() + "无法被识别为数值");
            }
            return val;
        }
        if (o instanceof List) {
            List list = (List) o;
            if (list.size() == 0) {
                throw new NumberFormatException(o.toString() + "无法将空List转为数值");
            }
            double val = Double.parseDouble(((List) o).get(0).toString());
            return val;
        }
        double val = toNumber(o.toString());
        return val;
    }

    private static boolean checkNumber(Object o) {
        if (o instanceof Number) return true;
        else if (o instanceof String) {
            Matcher matcher = numberPattern.matcher(StringUtils.trimToEmpty((String) o));
            if (matcher.find()) {
                return true;
            }
        } else if (o instanceof List) {
            List list = (List) o;
            if (list.size() > 0) {
                return checkNumber(list.get(0));
            }
        }
        return false;
    }

    private static boolean checkSize(List list, int size) {
        return list != null && list.size() >= size;
    }

    static final BiFunction<Object, List, Boolean> bgN = (args, params) -> {
        List<Double> arr = (List<Double>) args;
        return arr.get(0) - arr.get(1) > toNumber(params.get(0));
    };

    static final BiFunction<Object, List, Boolean> ltN = bgN.andThen(x -> !x);

    static final BiFunction<Object, List, Boolean> empty = (args, params) -> false;

    static ScriptContext scriptContext;

    static {
        scriptContext = ScriptContext.getInstance();
    }

    /**
     * TODO 暂未接入js引擎，后续应加入
     *
     * @param feature
     * @param op
     * @param signals
     * @param unitId
     * @return
     */
    public static BiFunction<Object, List, Boolean> getFunction(Feature feature, String op, List<Signal> signals, String unitId) {
        try {
            if (op == null) {
                op = feature.getString("opertion");
            }
            final String opertion = op;

            switch (opertion) {
                case ">":
                    return (a, params) -> checkNumber(a) && checkSize(params, 1) && toNumber(a) > toNumber(params.get(0));
                case "<":
                    return (a, params) -> checkNumber(a) && checkSize(params, 1) && toNumber(a) < toNumber(params.get(0));
                case ">=":
                    return (a, params) -> {
//                        System.out.println("--------->=------");
//                        System.out.println(a);
//                        System.out.println(params);
                        return checkNumber(a) && checkSize(params, 1) && toNumber(a) >= toNumber(params.get(0));
                    };
                case "<=":
                    return (a, params) -> checkNumber(a) && checkSize(params, 1) && toNumber(a) <= toNumber(params.get(0));
                case "=":
                    return (a, params) -> checkNumber(a) && checkSize(params, 1) && toNumber(a) == toNumber(params.get(0));
                case "between":
                    return (a, params) -> checkNumber(a) && checkSize(params, 2) && toNumber(a) >= toNumber(params.get(0)) && toNumber(a) <= toNumber(params.get(1));
                case "in":
                    return (a, params) -> {
                        for (Object param : params) {
                            if (param.toString().equals(a.toString())) {
                                return true;
                            }
                        }
                        return false;
                    };
                case "bgN":
                    return bgN;
                case "ltN":
                    return ltN;
                case "any>":
                    return (a, params) -> checkNumber(a) && checkSize(params, 1) && ((List<Number>) a).parallelStream().anyMatch(x -> x.doubleValue() > toNumber(params.get(0)));
                case "any>=":
                    return (a, params) -> checkNumber(a) && checkSize(params, 1) && ((List<Number>) a).parallelStream().anyMatch(x -> x.doubleValue() >= toNumber(params.get(0)));
                case "any<":
                    return (a, params) -> checkNumber(a) && checkSize(params, 1) && ((List<Number>) a).parallelStream().anyMatch(x -> x.doubleValue() < toNumber(params.get(0)));
                case "any<=":
                    return (a, params) -> checkNumber(a) && checkSize(params, 1) && ((List<Number>) a).parallelStream().anyMatch(x -> x.doubleValue() <= toNumber(params.get(0)));
                case "any=":
                    return (a, params) -> checkNumber(a) && checkSize(params, 1) && ((List<Number>) a).parallelStream().anyMatch(x -> x.doubleValue() == toNumber(params.get(0)));
                case "anyBetween":
                    return (a, params) -> checkNumber(a) && checkSize(params, 2) && ((List<Number>) a).parallelStream().anyMatch(x -> x.doubleValue() >= toNumber(params.get(0)) && x.doubleValue() <= toNumber(params.get(0)));
                case "all>":
                    return (a, params) -> checkNumber(a) && checkSize(params, 1) && ((List<Number>) a).parallelStream().allMatch(x -> x.doubleValue() > toNumber(params.get(0)));
                case "all>=":
                    return (a, params) -> checkNumber(a) && checkSize(params, 1) && ((List<Number>) a).parallelStream().allMatch(x -> x.doubleValue() >= toNumber(params.get(0)));
                case "all<":
                    return (a, params) -> checkNumber(a) && checkSize(params, 1) && ((List<Number>) a).parallelStream().allMatch(x -> x.doubleValue() < toNumber(params.get(0)));
                case "all<=":
                    return (a, params) -> checkNumber(a) && checkSize(params, 1) && ((List<Number>) a).parallelStream().allMatch(x -> x.doubleValue() <= toNumber(params.get(0)));
                case "all=":
                    return (a, params) -> checkNumber(a) && checkSize(params, 1) && ((List<Number>) a).parallelStream().allMatch(x -> x.doubleValue() == toNumber(params.get(0)));
                case "anyD>":
                    return (a, params) -> {
                        double maxValue = ((List<Number>) a).stream().map(n -> n.doubleValue()).sorted().max(Double::compareTo).orElse(0.0);
                        double minValue = ((List<Number>) a).stream().map(n -> n.doubleValue()).sorted().min(Double::compareTo).orElse(0.0);
                        return checkNumber(a) && checkSize(params, 1) && (maxValue - minValue > toNumber(params.get(0)));
                    };
                case "anyD>=":
                    return (a, params) -> checkNumber(a) && checkSize(params, 1) && (((List<Number>) a).stream().map(n -> n.doubleValue()).sorted().max(Double::compareTo).orElse(0.0) -
                            ((List<Number>) a).stream().map(n -> n.doubleValue()).sorted().min(Double::compareTo).orElse(0.0) >= toNumber(params.get(0)));
                case "allD<":
                    return getFunction(feature, "anyD>=", signals, unitId).andThen(x -> !x);
                case "allD<=":
                    return getFunction(feature, "anyD>", signals, unitId).andThen(x -> !x);
                default:
                    if (scriptContext.containsBinding(opertion)) {
                        JSObject jsObject = scriptContext.getServiceByBinding(opertion);
                        return (a, params) -> Boolean.parseBoolean(scriptContext.call(jsObject, a, params, opertion, signals, feature,unitId).toString());
                    } else if (StringUtils.startsWithIgnoreCase(opertion, "js:")) {
                        JSObject jsObject = scriptContext.getService("jsExpression");
                        return (a, params) -> {
                            try {
                                Object callResult = scriptContext.call(jsObject, a, params, StringUtils.substring(opertion, 3), signals, feature,unitId);
                                System.out.println("callResult = " + callResult);
                                return Boolean.parseBoolean(callResult.toString());
                            } catch (NashornException | IndexOutOfBoundsException nex) {
                                nex.printStackTrace();
                                return false;
                            }
                        };
                    }
            }
            return empty;
        } finally {

        }
    }

    public static void main(String... args) {
        Consumer<Matcher> matcherConsumer = (matcher) -> {
            if (matcher.find())
                System.out.println(String.join(",",
                        IntStream.range(0, matcher.groupCount()).mapToObj(i -> matcher.group(i)).collect(Collectors.toList())));
            else
                System.out.println("Not Matched");
        };

        matcherConsumer.accept(numberPattern.matcher("45.6Pa"));
        matcherConsumer.accept(numberPattern.matcher("47.21 m3/s"));
    }

}
